package com.g.ocache.impl.store;

import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;

import com.g.ocache.Cache;
import com.g.ocache.Store;
import com.g.ocache.StoreEntry;
import com.g.ocache.impl.store.expiration.ExpirationEvictor;


/**
 * 
 * @author zhangyf
 * @param <K>
 * @param <V>
 */
public final class FifoStore<K, V> extends AbstractSimpleStore<K, V> {
	//
	private final LinkedHashMap<K, StoreEntry<K, V>> values;
	
	/**
	 * 
	 */
	public FifoStore(Cache<K, V> cache, StoreEntry.Factory<K, V> sef, ExpirationEvictor.Factory<K, V> ef, int capacity) {
		this(cache, sef, ef, capacity, false);
	}

	public FifoStore(Cache<K, V> cache, StoreEntry.Factory<K, V> sef, ExpirationEvictor.Factory<K, V> ef, int capacity, boolean fair) {
		//
		super(cache, sef, capacity, ef, fair);
		
		//
		this.values = new LinkedHashMap<K, StoreEntry<K, V>>(DEFAULT_INITIAL_CAPACITY, DEFAULT_GROWTH_FACTOR, false);
	}
	
	/**
	 * 
	 */
	protected int doSize() {
		return values.size();
	}
	
	protected void doClear() {
		values.clear();
	}

	protected StoreEntry<K, V> doRemove(K key) {
		return values.remove(key);
	}
	
	protected StoreEntry<K, V> doGet(K key) {
		return values.get(key);
	}

	protected Map<K, StoreEntry<K, V>> doGetAll() {
		return values;
	}
	
	protected StoreEntry<K, V> doEvictIfNeeded(K key) {
		//
		if(values.containsKey(key)) {
			return null;
		}
		if(!isFull()) {
			return null;
		}
		
		//
		StoreEntry<K, V> entry = null;
		Iterator<K> iter = values.keySet().iterator();
		if (iter.hasNext()) {
			entry = values.remove(iter.next());
		}
		return entry;
	}

	protected StoreEntry<K, V> doPut(K key, StoreEntry<K, V> entry) {
		return values.put(key, entry);
	}
	
	/**
	 * 
	 */
	protected boolean isFull() {
		//
		if(getCapacity() <= 0) {
			return false;
		}
		
		//
		int size = 0;
		if(isLenient()) {
			size = values.size();
		} else {
			for(Entry<K, StoreEntry<K, V>> entry : values.entrySet()) {
				if(!entry.getValue().isExpired()) {
					size++;
				}
			}
		}
		return size >= getCapacity();
	}
	
	protected Map<K, V> doPeek(Filter<K, V> filter) {
		return doFilter(filter, true);
	}

	protected Map<K, V> doPoll(Filter<K, V> filter) {
		return doFilter(filter, false);
	}
	
	private Map<K, V> doFilter(Filter<K, V> filter, boolean readOnly) {
		Map<K, V> r = new HashMap<K, V>();
		for(Iterator<K> iter = values.keySet().iterator(); iter.hasNext(); ) {
			K key = iter.next();
			
			//
			StoreEntry<K, V> entry = values.get(key);
			if(entry != null && filter.accept(entry)) {
				//
				r.put(key, entry.getValue());
				
				//
				if(!readOnly) {
					iter.remove();
				}
			}
		}
		return r;
	}
	
	/**
	 * 
	 */
	public static class FactoryImpl<K, V> extends AbstractSimpleStore.AbstractFactory<K, V> {
		//
		private final int capacity;
		
		/**
		 * 
		 */
		public FactoryImpl(StoreEntry.Factory<K, V> sef, int capacity) {
			this(sef, null, capacity);
		}
		
		public FactoryImpl(StoreEntry.Factory<K, V> sef, ExpirationEvictor.Factory<K, V> ef, int capacity) {
			super(sef, ef);
			this.capacity = capacity;
		}

		/**
		 * 
		 */
		public Store<K, V> create(Cache<K, V> cache) {
			Store<K, V> r = new FifoStore<K, V>(cache, entryFactory, evictorFactory, capacity, fair);
			r.addStoreEventListener(getListener());
			return r;
		}
	}
}
